# v2로 마이그레이션하기

## 새로운 기능

### 새로운 [`wrap`](/docs/react/wrap) 빌더 [#270](https://github.com/toss/suspensive/pull/270)

컴포넌트를 `<Suspense/>`, `<ErrorBoundary/>`, `<ErrorBoundaryGroup/>`으로 한번에 래핑하는 새로운 기능입니다.

`<Suspense/>`, `<ErrorBoundary/>`, `<ErrorBoundaryGroup/>` 등의 경우 많은 사람들이 HOC를 사용하여 이러한 구성 요소를 구성 요소 주위에 래핑합니다. 이 컴포넌트들은 children에 어떤 처리를 요하기 때문입니다. 그래서 컴포넌트를 불필요하게 나누지 않고 depth를 만들지 않기 위해 각 interface를 위한 HOC인 withErrorBoundary, withErrorBoundaryGroup, withSuspense를 사용하지만 각 HOC를 조합해서 사용하는 경우도 자주 발생하면서 가독성 또한 개선할 필요가 있었습니다. 이를 개선하기 위해 wrap을 제공하기로 했습니다.

```jsx
import { wrap } from '@suspensive/react'
import { useSuspenseQuery } from '@suspensive/react-query'

const Example = wrap
  .ErrorBoundaryGroup({ blockOutside: false })
  .ErrorBoundary({
    fallback: ({ error }) => <>{error.message}</>,
    onError: logger.log,
  })
  .Suspense({ fallback: <>loading...</>, clientOnly: true })
  .on(() => {
    const query = useSuspenseQuery({
      queryKey: ['key'],
      queryFn: () => api.text(),
    })
    return <>{query.data.text}</>
  })
```

### 새로운 `<ErrorBoundary/>`의 `shouldCatch` prop [#569](https://github.com/toss/suspensive/pull/569)

Suspensive의 `<ErrorBoundary/>`는 children에서 발생된 모든 thrown error를 잡아낼 수 있습니다. 하지만 모든 thrown error를 잡아내기 때문에 `<ErrorBoundary/>`를 사용할 때 더 좁은 위치에 `<ErrorBoundary/>`를 놓는 것을 고민하게 되었습니다.
이 때문에 어떤 Error를 잡아내야 할지 설정할 수 있는 shouldCatch라는 새 prop을 ErrorBoundary에 추가하게 되었습니다.

![ErrorBoundary shouldCatch example](/img/errorBoundary-shouldcatch-prop.png)

1. `shouldCatch`: ErrorConstructor

```jsx
import { ErrorBoundary } from '@suspensive/react'

class CustomError extends Error {}

const Example = () => {
  return (
    <ErrorBoundary fallback={({ error }) => <>RestError: {error.message}</>}>
      <ErrorBoundary
        shouldCatch={CustomError}
        onError={logOnCustomError}
        fallback={({ error }) => <>CustomError: {error.message}</>}
      >
        <ThrowErrorComponent />
      </ErrorBoundary>
    </ErrorBoundary>
  )
}
```

2. `shouldCatch`: callback

```jsx
import { ErrorBoundary } from '@suspensive/react'

class CustomError extends Error {}

const Example = () => {
  return (
    <ErrorBoundary fallback={({ error }) => <>RestError: {error.message}</>}>
      <ErrorBoundary
        shouldCatch={(error) => error instanceof CustomError}
        onError={logOnCustomError}
        fallback={({ error }) => <>CustomError: {error.message}</>}
      >
        <ThrowErrorComponent />
      </ErrorBoundary>
    </ErrorBoundary>
  )
}
```

3. `shouldCatch`: boolean

```jsx
import { ErrorBoundary } from '@suspensive/react'

class CustomError extends Error {}

const Example = () => {
  return (
    <ErrorBoundary fallback={({ error }) => <>RestError: {error.message}</>}>
      <ErrorBoundary
        shouldCatch={new Date().toISOString() > '2024-01-01T00:00:00.000Z'}
        onError={logOnErrorAfter2024}
        fallback={({ error }) => <>ErrorAfter2024: {error.message}</>}
      >
        <ThrowErrorComponent />
      </ErrorBoundary>
    </ErrorBoundary>
  )
}
```

### 새로운 [`<ErrorBoundary.Consumer/>`](/docs/react/ErrorBoundary), [`<ErrorBoundaryGroup.Consumer/>`](/docs/react/ErrorBoundaryGroup) 컴포넌트 [#610](https://github.com/toss/suspensive/pull/610)

이 컴포넌트는 jsx에서 `useErrorBoundary`, `useErrorBoundaryGroup`을 인라인으로 사용할 수 있습니다.

```jsx
import { ErrorBoundary, ErrorBoundaryGroup } from '@suspensive/react'

const Example = () => {
  return (
    <ErrorBoundaryGroup>
      <ErrorBoundaryGroup.Consumer>
        {({ reset }) => <button onClick={reset}>reset all</button>}
      </ErrorBoundaryGroup.Consumer>
      <ErrorBoundary fallback={({ error }) => <>{error.message}</>}>
        <ErrorBoundary.Consumer>
          {({ setError }) => (
            <button onClick={() => setError(new Error('error message'))}>
              setError
            </button>
          )}
        </ErrorBoundary.Consumer>
      </ErrorBoundary>
    </ErrorBoundaryGroup>
  )
}
```

## BREAKING CHANGES 처리하기

### `<AsyncBoundary/>` 제거

v2에서는 `<AsyncBoundary/>`를 제거했습니다. [#295](https://github.com/toss/suspensive/issues/295)

`<AsyncBoundary/>`는 내부적으로 `<ErrorBoundary/>`를 사용하기 때문에 `useErrorBoundary`와 함께 사용할 수 있으며 `<ErrorBoundaryGroup/>`의 영향을 받습니다. 우리는 라이브러리 사용자를 위한 유지 관리 및 인터페이스 통합에 더 좋을 것이라고 믿고 이 구성 요소를 v2에서 제거하기로 결정했습니다.

`<AsyncBoundary/>`의 기능은 두 컴포넌트(`<Suspense/>`, `<ErrorBoundary/>`)를 하나씩 래핑하는 것입니다.
그러면 이렇게 2개로 나눌 수 있습니다.

```diff
+ import { Suspense, ErrorBoundary } from '@suspensive/react'
- import { AsyncBoundary } from '@suspensive/react'

+ <ErrorBoundary fallback={<Error />} onError={onError} onReset={onReset}>
+   <Suspense fallback={<Loading />}>
+     <Children />
+   </Suspense>
+ </ErrorBoundary>
- <AsyncBoundary pendingFallback={<Loading />} rejectedFallback={<Error />} onError={onError} onReset={onReset}>
-   <Children />
- </AsyncBoundary>
```

### `withSuspense`, `withDelay`, `withErrorBoundary`, `withErrorBoundaryGroup` 제거

이러한 모든 HOC는 v2의 새로운 HOC 빌더 `wrap`으로 아름답게 대체될 수 있습니다.

```diff
+ import { wrap } from '@suspensive/react'
- import { withSuspense, withErrorBoundary, withErrorBoundaryGroup } from '@suspensive/react'

+ const Example = wrap
+   .ErrorBoundaryGroup({ blockOutside: false })
+   .ErrorBoundary({ fallback: ({ error }) => <>{error.message}</>, onError: logger.log })
+   .Suspense({ fallback: <>loading...</>, clientOnly: true })
+   .on(() => {
+     const query = useSuspenseQuery({
+       queryKey: ['key'],
+       queryFn: () => api.text(),
+     })
+     return <>{query.data.text}</>
+   })
- const Example = withErrorBoundaryGroup(
-   withErrorBoundary(
-     withSuspense(
-       () => {
-         const query = useSuspenseQuery({
-           queryKey: ['key'],
-           queryFn: () => api.text(),
-         })
-         return <>{query.data.text}</>
-       },
-       { fallback: <>loading...</>, clientOnly: true }
-     ),
-     { fallback: ({ error }) => <>{error.message}</>, onError: logger.log }
-   ),
-   { blockOutside: false }
- )
```

```diff
+ import { wrap } from '@suspensive/react'
- import { withSuspense } from '@suspensive/react'

+ const Example = wrap
+   .Suspense({
+     fallback: <>loading...</>,
+     clientOnly: true,
+   })
+   .on(() => {
+     const query = useSuspenseQuery({
+       queryKey: ['key'],
+       queryFn: () => api.text(),
+     })
+     return <>{query.data.text}</>
+   })
- const Example = withSuspense(
-   () => {
-     const query = useSuspenseQuery({
-       queryKey: ['key'],
-       queryFn: () => api.text(),
-     })
-     return <>{query.data.text}</>
-   },
-   {
-     fallback: <>loading...</>,
-     clientOnly: true,
-   }
- )
```

### `<ErrorBoundaryGroup.Reset/>` 제거

`<ErrorBoundaryGroup.Reset/>`은 내부적으로 `useErrorBoundaryGroup`을 사용합니다. 그래서 우리는 이를 Context.Consumer와 같은 것으로 변경하면 React 개발자가 이 컴포넌트의 동작을 더 쉽게 이해할 수 있을 것이라고 생각했습니다. 이름을 `<ErrorBoundaryGroup.Consumer/>`로 변경하고 인터페이스를 Context.Consumer와 동일하게 유지했습니다.

```diff
import { ErrorBoundaryGroup } from '@suspensive/react'

const Example = () => {
  return (
    <ErrorBoundaryGroup>
-     <ErrorBoundaryGroup.Reset trigger={(group) => <button onClick={group.reset}>reset all</button>} />
+     <ErrorBoundaryGroup.Consumer>
+       {(group) => <button onClick={group.reset}>reset all</button>}
+     </ErrorBoundaryGroup.Consumer>
    </ErrorBoundaryGroup>
  )
}
```

### `defaultOptions` → `Suspensive`의 `defaultProps`로 이름 변경

```diff
import { ErrorBoundaryGroup } from '@suspensive/react'

const suspensive = new Suspensive({
- defaultOptions: {
+ defaultProps: {
    suspense: {
      fallback: 'default loading...',
    },
  },
})
```

### `<Suspense.CSROnly/>` → `<Suspense clientOnly/>`를 이름 변경(prop으로)

```diff
import { Suspense } from '@suspensive/react'

const Example = () => {
  return (
-   <Suspense.CSROnly fallback={<>loading...</>}>
+   <Suspense clientOnly fallback={<>loading...</>}>
      <>children</>
    </Suspense>
  )
}
```
